﻿//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------

using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xaml;

using Microsoft.CSharp.RuntimeBinder;
using Microsoft.M;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;
using Microsoft.Scripting.Hosting.Providers;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;

using Supremacy.Scripting.Ast;
using Supremacy.Scripting.Runtime;

using Binder = Microsoft.CSharp.RuntimeBinder.Binder;
using CompilerOptions = Microsoft.M.CompilerOptions;
using Expression = Supremacy.Scripting.Ast.Expression;
using MSAst = System.Linq.Expressions;

namespace PersonExample
{
    using System;
    using System.Collections.Generic;
    using System.Dataflow;
    using System.IO;

    using MGraphXamlReader;

    public static class Example
    {

        static string _testInput = @"
local s = 5
select 5e5
";

        static Parser LoadExampleGrammar()
        {
            var assembly = Assembly.GetExecutingAssembly();
            var mImage = (from name in assembly.GetManifestResourceNames()
                          where name.EndsWith("ExpressionLanguage.mx")
                          select name).Single();

            using (var options = new CompilerOptions())
            {
                using (var stream = assembly.GetManifestResourceStream(mImage))
                {
                    using (var image = new MImage(stream, true))
                    {
                        var factory = image.ParserFactories["Supremacy.Scripting.ExpressionLanguage"];
                        return factory.Create();
                    }
/*
                    using (var streamReader = new StreamReader(stream))
                    {
                        var sourceItems = new SourceItem[]
                                          {
                                              new TextItem()
                                              {
                                                  Name = "Test Grammar",
                                                  ContentType = TextItemType.MGrammar,
                                                  Reader = streamReader
                                              }
                                          };

                        options.AddSourceItems(new SourceItem[]);
                        options.AddSourceItems(sourceItems);

                        var results = Compiler.Compile(TargetTypes.Parser, options);
                        if (results.HasErrors)
                        {
                            throw new Exception("Failure compiling Test Grammar");
                        }

                        return results.ParserFactories.Single().Value.Create();
                    }
*/
                }
            }
        }

        public class Order {
            private readonly int _id;

            public int ID
            {
                get { return _id; }
            }

            public Order(int id)
            {
                _id = id;
            }
        }

        public class Customer {
            private readonly string _name;
            private readonly Order[] _orders;

            public string Name
            {
                get { return _name; }
            }

            public Order[] Orders
            {
                get { return _orders; }
            }

            public Customer(string name, Order[] orders)
            {
                _name = name;
                _orders = orders;
            }

            public override string ToString()
            {
                return this.Name;
            }
        }

        public static void RunExample()
        {
            var parser = LoadExampleGrammar();
            var assembly = Assembly.GetAssembly(typeof(Ast));

            var xamlMap = assembly
                .GetTypes()
                .Where(o => o.Namespace == "Supremacy.Scripting.Ast" && o.IsPublic)
                .ToDictionary(o => (Identifier)o.Name, o => o);

            //var testInput = (from name in assembly.GetManifestResourceNames()
            //                 where name.EndsWith("ExpressionTests.es")
            //                 select name).Single();

            //using (var stream = assembly.GetManifestResourceStream(testInput))
            {
                //System.Diagnostics.Debug.Assert(stream != null);

                var queryText = @"from c in customers from o in c.Orders let q = o orderby c.Name, q.ID descending select new Dictionary<object, object> { { c.Name, q.ID }, { c.Name.ToUpperInvariant(), (double?)(q.ID * -2d) }, { ""expr"", (System.Linq.Expressions.Expression<Func<int>>)(() => 5) } }";//"new double[][] { new double[5]{1, 2, 3, 4, 5}, new double[]{3,2,1}, new[]{ 5, 3, 4, 1.0, 2 } }";
                var people = parser.Parse<Expression>(/*new StreamReader(stream)*/ new StringReader(queryText), xamlMap);
                //var people = (Expression)XamlServices.Load(File.OpenText("graph.xaml"));

/*
                var qz = from c in customers
                         from o in c.Orders
                         let q = o
                         orderby c.Name , q.ID descending
                         select
                             new Dictionary<object, object>
                             { { c.Name, q.ID }, { c.Name.ToUpperInvariant(), (double?)(q.ID * -2d) }, { "expr", (MSAst.Expression<Func<int>>)(() => 5) } };
*/

                LinkParents.Link(people);

                var languageName = typeof(ScriptLanguageContext).AssemblyQualifiedName;
                var scriptRuntimeSetup = new ScriptRuntimeSetup { LanguageSetups = { new LanguageSetup(languageName, "Q#") } };
                var scriptEngine = ScriptRuntime.CreateRemote(AppDomain.CurrentDomain, scriptRuntimeSetup).GetEngineByTypeName(languageName);
                var language = HostingHelpers.GetLanguageContext(scriptEngine);
                var source = language.CreateSnippet(queryText, SourceCodeKind.Expression);
                var compilerContext = new CompilerContext(source, new Microsoft.Scripting.CompilerOptions(), new ErrorCounter());
                var parseContext = new Supremacy.Scripting.Runtime.ParseContext(compilerContext)
                                   {
                                       CurrentScope = new TopLevelScope(
                                           compilerContext,
                                           new ParametersCompiled(
                                               new Parameter(
                                                   "customers",
                                                   null,
                                                   Microsoft.Scripting.SourceSpan.None)
                                                   {
                                                    ParameterType = typeof(IEnumerable<Customer>)
                                                   }),
                                           people.Span.Start)
                                   };

                AstInitializer.Initialize(parseContext, people);

                var stringWriter = new StringWriter();
                var sourceWriter = new SourceWriter(stringWriter, " ");

                people.Dump(sourceWriter, 0);
                sourceWriter.Flush();
                Console.WriteLine(stringWriter.ToString());

                var resolved = people.Resolve(parseContext);

                //QueryRewriter.Rewrite(people);

                stringWriter = new StringWriter();
                sourceWriter = new SourceWriter(stringWriter, " ");

                resolved.Dump(sourceWriter, 0);

                var codeDump = stringWriter.ToString();

                Console.WriteLine();
                Console.WriteLine("--------------------------------------------------------------------------------");
                Console.WriteLine(codeDump);

                var generator = new ScriptGenerator((ScriptLanguageContext)language, compilerContext);
                var customers = new[]
                                {
                                    new Customer("Adrian", new[] { new Order(1), new Order(2) }),
                                    new Customer("John", new[] { new Order(3), new Order(4), new Order(5) }),
                                    new Customer("Wesley", new[] { new Order(6) }),
                                };

                parseContext.CurrentScope.TopLevel.Parameters[0].Scope = parseContext.CurrentScope;
                generator.Scope.GetOrMakeLocal("customers", typeof(IEnumerable<Customer>));

                var parameters = generator.Scope.Locals.ToList().AsReadOnly();
                var transformed = resolved.Transform(generator);
                var invokerLambda = generator.Scope.FinishScope(transformed);

                var invokeBinder =(InvokeBinder) Binder.Invoke(
                    CSharpBinderFlags.None,
                    null,
                    new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
                invokeBinder = generator.Binder.Invoke(1);

                var customersExpr = System.Linq.Expressions.Expression.Constant(customers);
                var invoke = invokeBinder.FallbackInvoke(
                    new DynamicMetaObject(invokerLambda, BindingRestrictions.GetTypeRestriction(invokerLambda, invokerLambda.Type)),
                    new[] { new DynamicMetaObject(customersExpr, BindingRestrictions.GetTypeRestriction(customersExpr, customersExpr.Type)) });

                var wtf = new[] { 5, 5.0 };
                var invoker = invokerLambda.Compile().DynamicInvoke(new object[] { customers });
            }

            Console.WriteLine();
        }

        public static void Main()
        {
            RunExample();
            if (System.Diagnostics.Debugger.IsAttached)
            {
                Console.WriteLine("Press ENTER to exit application");
                Console.ReadLine();
            }
        }
    }

    // Generic just to test the point...
    public class Person
    {
        List<Person> children = new List<Person>();

        public string First { get; set; }
        public string Last { get; set; }
        public ICollection<Person> Children { get { return children; } }

        public void Print(int depth)
        {
            Console.WriteLine(
                new String(' ', depth) +
                "I am a person named: " +
                First.ToString() +
                " " +
                Last.ToString() +
                ", and I have " +
                Children.Count +
                " Children:");

            foreach (var child in Children)
            {
                child.Print(depth + 1);
            }
        }
    }
}